/**
 *
 */
package com.wedroid.r2d2.contact.handler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;

import com.wedroid.entity.contact.Contacter;
import com.wedroid.entity.contact.ContacterProfile;
import com.wedroid.entity.contact.ContacterProfile.ContactProfileBuilder;
import com.wedroid.entity.contact.ContacterType;
import com.wedroid.entity.contact.Droid;
import com.wedroid.r2d2.cache.PersistentAccessor;
import com.wedroid.r2d2.contact.Contact;
import com.wedroid.r2d2.resource.ResourceManager;
import com.wedroid.r2d2.service.ContactService;

/**
 * @author lute
 */
@Component("jdbcContactHandler")
public class JdbcContactHandler implements ContactHandler {

    private static final Logger logger = LoggerFactory.getLogger(JdbcContactHandler.class);

    private static final int MAX_THREAD = 10;

    @Autowired
    private PersistentAccessor accessor;

    @Autowired
    private ContactService contactService;

    @Autowired
    private ResourceManager resourceManager;

    @Autowired
    private TaskExecutor taskExecutor;

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.handler.ContactHandler#handleContacts(java.util.List)
     */
    public void handleContacts(final String droidName, final List<Contact> contacts) {

        Map<Contact, Contacter> contactMap = new HashMap<>();

        for (Contact contact : contacts) {
            ContacterProfile profile = toContacterProfile(contact);

            // If contact is droid self, first handle it for droid. Then, hand self
            // as normal contact in case that ContactResponse do not return selc contact
            if (contact.isSelf()) {
                handleDroid(droidName, profile);
            }

            Contacter contacter = handleContactsInternal(droidName, contact);
            contactMap.put(contact, contacter);
        }

        int totalTasks = contactMap.size();
        int tasksPerThread = (totalTasks + MAX_THREAD - 1) / MAX_THREAD;  // => ceiling
        int idleThreads = tasksPerThread * MAX_THREAD - totalTasks;

        final List<Contact> keys = new ArrayList<>(contactMap.keySet());
        for (int i = 0; i < MAX_THREAD; i++) {
            int fromIdx = i < idleThreads ? (tasksPerThread - 1) * i : (tasksPerThread - 1) * idleThreads + tasksPerThread * (i - idleThreads);
            int tasks = i < idleThreads ? tasksPerThread - 1 : tasksPerThread;
            if (tasks > 0) {
                int toIdx = fromIdx + tasks;
                List<Contact> partialKeys = keys.subList(fromIdx, toIdx);

                // Get partial contact Map
                Map<Contact, Contacter> partialContactMap = new HashMap<>();
                for (Contact key : partialKeys) {
                    partialContactMap.put(key, contactMap.get(key));
                }

                ContactAvatarTask task = new ContactAvatarTask(partialContactMap);
                taskExecutor.execute(task);
            }
        }
    }

    /**
     * @param droidName
     * @param profile
     */
    private void handleDroid(final String droidName, final ContacterProfile profile) {
        Droid droid = contactService.getDroid(droidName);
        if (droid == null) {
            droid = new Droid(profile);
            contactService.createDroid(droid);
        }
    }

    /**
     * @param droidName
     * @param contact
     */
    private Contacter handleContactsInternal(final String droidName, final Contact contact) {
        Droid droid = accessor.getDroid(droidName);
        if (droid != null) {

            ContacterProfile profile = toContacterProfile(contact);
            ContacterType contacterType = contact.getContacterType();
            Contacter contacter = new Contacter(profile, contacterType, droid);

            contactService.createOrUpdateContacter(contacter);
            return contacter;
        } else {
            logger.warn("#handleContactsInternal - Can not find droid with name [{}]", droidName);
            return null;
        }
    }

    /**
     * @param contact
     * @return
     */
    private ContacterProfile toContacterProfile(final Contact contact) {
        return new ContactProfileBuilder().nickName(contact.getNickName())
                                          .remarkName(contact.getRemarkName())
                                          .displayName(contact.getDisplayName())
                                          .contactFlag(contact.getContactFlag())
                                          .memberCount(contact.getMemberCount())
                                          .alias(contact.getAlias())
                                          .sex(contact.getSex())
                                          .province(contact.getProvince())
                                          .city(contact.getCity())
                                          .signature(contact.getSignature())
                                          .build();
    }

    private class ContactAvatarTask implements Runnable {

        private Map<Contact, Contacter> contacters;

        /**
         * @param contacters
         */
        public ContactAvatarTask(Map<Contact, Contacter> contacters) {
            this.contacters = contacters;
        }

        /* (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        public void run() {
            for (Contact contact : contacters.keySet()) {
                String headImgUrl = resourceManager.requestAvatar(contact);
                Contacter contacter = contacters.get(contact);
                contactService.updateContacter(contacter.headImgUrl(headImgUrl));
            }
        }

    }

}
